/*
 ============================================================================
 Name        : hev-server.c
 Author      : Heiher <admin@heiher.info>
 Version     : 0.0.2
 Copyright   : Copyright (c) 2010 everyone.
 Description : Linkage 802.1x for Unix-like operating system.
 ============================================================================
 */

#include <netinet/if_ether.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>
#include <unistd.h>

#include "hev-server.h"
#include "hev-config.h"
#include "hev-receiver.h"
#include "hev-sender.h"

#define HEV_SERVER_LOST_PACKET_TRY_NUMBER			5
#define HEV_SERVER_LOST_PACKET_TRY_TIMEOUT			3

static gint hev_server_get_local_mac(HevServer * self);
static gboolean hev_server_timeout_handler(gpointer data);
static gboolean hev_server_find_server(HevServer * self);
static gboolean hev_server_auth_account_name(HevServer * self);
static gboolean hev_server_auth_account_passwd(HevServer * self);
static gboolean hev_server_logout(HevServer * self);
static void hev_server_receiver_real_received(HevReceiver * receiver, gpointer pdata, gpointer data);
static gint hev_server_open_io(HevServer * self);
static void hev_server_close_io(HevServer * self);

enum
{
	PROP_0,
	PROP_STATE,
	PROP_CONFIG,
	N_PROPERTIES
};

enum
{
	STATUS_CHANGED,
	LAST_SIGNAL
};

static guint hev_server_signals[LAST_SIGNAL] = { 0 };
static GParamSpec * hev_server_properties[N_PROPERTIES] = { NULL };

#define HEV_SERVER_GET_PRIVATE(obj)	(G_TYPE_INSTANCE_GET_PRIVATE((obj), HEV_TYPE_SERVER, HevServerPrivate))

typedef struct _HevServerPrivate HevServerPrivate;

struct _HevServerPrivate
{
	GObject * config;
	GObject * receiver;
	GObject * sender;

	guchar local_mac[ETH_ALEN];
	guchar remote_mac[ETH_ALEN];
	gint try_number;
	gint autotry_number;
	HevServerState state;
	const guchar * packet;
	guint timeout_id;
};

static void hev_server_initable_iface_init(GInitableIface * iface);
static gboolean hev_server_initable_init(GInitable * initable,
					GCancellable * cancellable, GError ** error);
static void hev_server_set_property(GObject * obj, guint id,
				const GValue * value, GParamSpec * pspec);
static void hev_server_get_property(GObject * obj, guint id,
				GValue * value, GParamSpec * pspec);

G_DEFINE_TYPE_WITH_CODE(HevServer, hev_server, G_TYPE_OBJECT,
		G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, hev_server_initable_iface_init));

static void hev_server_dispose(GObject * obj)
{
	HevServer * self = HEV_SERVER(obj);
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	/* Logout START */
	if(HEV_SERVER_AUTH_SUCC == priv->state)
	{
		priv->state = HEV_SERVER_NULL;
		hev_server_logout(self);
	}
	/* Logout END */

	/* receiver START */
	if(G_IS_OBJECT(priv->receiver))
	{
		g_object_unref(priv->receiver);

		priv->receiver = NULL;
	}
	/* receiver END */

	/* sender START */
	if(G_IS_OBJECT(priv->sender))
	{
		g_object_unref(priv->sender);

		priv->sender = NULL;
	}
	/* sender END */

	/* config START */
	if(G_IS_OBJECT(priv->config))
	{
		g_object_unref(priv->config);

		priv->config = NULL;
	}
	/* config END */

	G_OBJECT_CLASS(hev_server_parent_class)->dispose(obj);
}

static void hev_server_finalize(GObject * obj)
{
	HevServer * self = HEV_SERVER(obj);
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	G_OBJECT_CLASS(hev_server_parent_class)->finalize(obj);
}

static GObject * hev_server_constructor(GType type, guint n, GObjectConstructParam * param)
{
	return G_OBJECT_CLASS(hev_server_parent_class)->constructor(type, n, param);
}

static void hev_server_constructed(GObject * obj)
{
}

static void hev_server_class_init(HevServerClass * klass)
{
	GObjectClass * obj_class = G_OBJECT_CLASS(klass);

	obj_class->constructor = hev_server_constructor;
	obj_class->constructed = hev_server_constructed;
	obj_class->dispose = hev_server_dispose;
	obj_class->finalize = hev_server_finalize;
	obj_class->set_property = hev_server_set_property;
	obj_class->get_property = hev_server_get_property;

	hev_server_properties[PROP_STATE] =
			g_param_spec_uint("state", "State", "Server state", 0, G_MAXUINT,
					HEV_SERVER_NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);

	hev_server_properties[PROP_CONFIG] =
			g_param_spec_object("config", "Config", "Server config", HEV_TYPE_CONFIG,
								G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);

	g_object_class_install_properties(obj_class, N_PROPERTIES, hev_server_properties);

	hev_server_signals[STATUS_CHANGED] = g_signal_new("state_changed",
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET(HevServerClass, state_changed),
			NULL, NULL,
			g_cclosure_marshal_VOID__INT,
			G_TYPE_NONE, 1,
			G_TYPE_INT);

	g_type_class_add_private(klass, sizeof(HevServerPrivate));
}

static void hev_server_initable_iface_init(GInitableIface * iface)
{
	iface->init = hev_server_initable_init;
}

static void hev_server_init(HevServer * self)
{
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	priv->state = HEV_SERVER_NULL;
	priv->autotry_number = 0;
}

static gboolean hev_server_initable_init(GInitable * initable,
					GCancellable * cancellable, GError ** error)
{
	HevServer * self = HEV_SERVER(initable);
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	/* config START */
	priv->config = hev_config_new(NULL, error);
	if(NULL == priv->config)
	{
		return FALSE;
	}
	/* config END */

	return TRUE;
}

static void hev_server_set_property(GObject * obj, guint id,
				const GValue * value, GParamSpec * pspec)
{
	HevServer * self = HEV_SERVER(obj);
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	switch(id)
	{
	case PROP_STATE:
		{
			priv->state = g_value_get_uint(value);
			g_object_notify(obj, "state");
			break;
		}
	case PROP_CONFIG:
		{
			priv->config = g_value_get_object(value);
			g_object_notify(obj, "config");
			break;
		}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
		break;
	}
}

static void hev_server_get_property(GObject * obj, guint id,
				GValue * value, GParamSpec * pspec)
{
	HevServer * self = HEV_SERVER(obj);
	HevServerPrivate * priv = HEV_SERVER_GET_PRIVATE(self);

	switch(id)
	{
	case PROP_STATE:
		{
			g_value_set_uint(value, priv->state);
			break;
		}
	case PROP_CONFIG:
		{
			g_value_set_object(value, priv->config);
			break;
		}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
		break;
	}
}

GObject * hev_server_new(GCancellable * cancellable, GError ** error)
{
	return g_initable_new(HEV_TYPE_SERVER, cancellable, error, NULL);
}

gint hev_server_start(HevServer * self)
{
	HevServerPrivate * priv = NULL;

	g_return_val_if_fail(HEV_IS_SERVER(self), -1);

	priv = HEV_SERVER_GET_PRIVATE(self);

	if((HEV_SERVER_NULL==priv->state) ||
			(HEV_SERVER_AUTH_FAIL_FINDING_SERVER==priv->state) ||
			(HEV_SERVER_AUTH_FAIL_ACCOUNT_NAME==priv->state) ||
			(HEV_SERVER_AUTH_FAIL_ACCOUNT_PASSWD==priv->state) ||
			(HEV_SERVER_AUTH_FAIL_SUCC==priv->state) ||
			(HEV_SERVER_TIMEOUT==priv->state))
	{
		/* local_mac START */
		if(-1 == hev_server_get_local_mac(self))
		{
			return -1;
		}
		/* local_mac END */

		if(-1 == hev_server_open_io(self))
		{
			return -1;
		}

		/* start find server START */
		priv->state = HEV_SERVER_FINDING_SERVER;
		priv->try_number = 1;
		g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
				0, priv->state);
		if(FALSE == hev_server_find_server(self))
		{
			goto fail;
		}
		/* start find server END */

		priv->timeout_id = g_timeout_add_seconds(HEV_SERVER_LOST_PACKET_TRY_TIMEOUT,
				hev_server_timeout_handler, self);
		if(0 == priv->timeout_id)
		{
			goto fail;
		}
	}

	return 0;

fail:
	hev_server_close_io(self);

	return -1;
}

void hev_server_stop(HevServer * self)
{
	HevServerPrivate * priv = NULL;

	g_return_if_fail(HEV_IS_SERVER(self));

	priv = HEV_SERVER_GET_PRIVATE(self);

	/* Logout START */
	if((HEV_SERVER_NULL!=priv->state) &&
			(HEV_SERVER_AUTH_FAIL_FINDING_SERVER!=priv->state) &&
			(HEV_SERVER_AUTH_FAIL_ACCOUNT_NAME!=priv->state) &&
			(HEV_SERVER_AUTH_FAIL_ACCOUNT_PASSWD!=priv->state) &&
			(HEV_SERVER_AUTH_FAIL_SUCC!=priv->state) &&
			(HEV_SERVER_TIMEOUT!=priv->state))
	{
		priv->state = HEV_SERVER_NULL;
		priv->try_number = 0;
		priv->autotry_number = 0;
		g_source_remove(priv->timeout_id);
		priv->timeout_id = 0;
		g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
				0, priv->state);
		hev_server_logout(self);
		hev_server_close_io(self);
	}
	/* Logout END */
}

static gint hev_server_get_local_mac(HevServer * self)
{
	HevServerPrivate * priv = NULL;
	gint sockfd = 0;
	gchar * interface = NULL;
	struct ifreq ifr;

	g_return_val_if_fail(HEV_IS_SERVER(self), -1);

	priv = HEV_SERVER_GET_PRIVATE(self);

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(0 > sockfd)
	{
		g_critical("[%s -> socket]", __FUNCTION__);
		return -1;
	}
	interface = hev_config_get_general_interface(HEV_CONFIG(priv->config));
	if(NULL == interface)
	{
		g_critical("[%s -> hev_config_get_general_interface]", __FUNCTION__);
		return -1;
	}
	ifr.ifr_ifru.ifru_addr.sa_family = AF_INET;
	strncpy(ifr.ifr_ifrn.ifrn_name, interface, IFNAMSIZ);
	g_free(interface);
	if(-1 == ioctl(sockfd, SIOCGIFHWADDR, &ifr))
	{
		g_critical("[%s -> ioctl]", __FUNCTION__);
		goto fail;
	}
	g_memmove(priv->local_mac, ifr.ifr_ifru.ifru_hwaddr.sa_data, ETHER_ADDR_LEN);
	close(sockfd);

	return 0;
fail:
	close(sockfd);
	return -1;
}

static void hev_server_receiver_real_received(HevReceiver * receiver, gpointer pdata, gpointer data)
{
	HevServer * self = HEV_SERVER(data);
	HevServerPrivate * priv = NULL;
	const guchar * packet = pdata;

	g_return_if_fail(HEV_IS_SERVER(self));

	priv = HEV_SERVER_GET_PRIVATE(self);
	priv->packet = packet;
	switch(packet[0x12]) /* EAP packet type */
	{
	case 0x01: /* server request */
	{
		switch(packet[0x16]) /* request type */
		{
		case 0x01:	/* account name */
		{
			if(HEV_SERVER_FINDING_SERVER == priv->state)
			{
				priv->state = HEV_SERVER_AUTH_ACCOUNT_NAME;
				priv->try_number = 1;
				g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
						0, priv->state);
				g_memmove(priv->remote_mac, packet+0x06, ETH_ALEN); /* copy server mac */
			}
			else if(HEV_SERVER_AUTH_SUCC == priv->state) /* Keepalive */
			{
			}
			hev_server_auth_account_name(self);
			break;
		}
		case 0x99:	/* account passwd */
		{
			if(HEV_SERVER_AUTH_ACCOUNT_NAME == priv->state)
			{
				priv->state = HEV_SERVER_AUTH_ACCOUNT_PASSWD;
				priv->try_number = 1;
				g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
						0, priv->state);
				hev_server_auth_account_passwd(self);
			}
			break;
		}
		default:
			break;
		}
		break;
	}
	case 0x03: /* auth success */
	{
		if(HEV_SERVER_AUTH_ACCOUNT_PASSWD == priv->state)
		{
			priv->state = HEV_SERVER_AUTH_SUCC;
			priv->try_number = 0;
			g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
					0, priv->state);
		}
		break;
	}
	case 0x04: /* auth fail */
	{
		gint s = HEV_SERVER_NULL;

		switch(priv->state)
		{
		case HEV_SERVER_NULL:
		case HEV_SERVER_TIMEOUT:
		case HEV_SERVER_AUTH_FAIL_FINDING_SERVER:
		case HEV_SERVER_AUTH_FAIL_ACCOUNT_NAME:
		case HEV_SERVER_AUTH_FAIL_ACCOUNT_PASSWD:
		case HEV_SERVER_AUTH_FAIL_SUCC:
			break;
		case HEV_SERVER_FINDING_SERVER:
			s = HEV_SERVER_AUTH_FAIL_FINDING_SERVER;
			break;
		case HEV_SERVER_AUTH_ACCOUNT_NAME:
			s = HEV_SERVER_AUTH_FAIL_ACCOUNT_NAME;
			break;
		case HEV_SERVER_AUTH_ACCOUNT_PASSWD:
			s = HEV_SERVER_AUTH_FAIL_ACCOUNT_PASSWD;
			break;
		case HEV_SERVER_AUTH_SUCC:
			s = HEV_SERVER_AUTH_FAIL_SUCC;
			break;
		}

		if(HEV_SERVER_AUTH_FAIL_SUCC != s)
		{
			g_source_remove(priv->timeout_id);
			priv->timeout_id = 0;
		}
		priv->try_number = 0;
		priv->state = s;
		g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
					0, priv->state);
		hev_server_close_io(self);

		if(hev_config_get_general_autoretry(HEV_CONFIG(priv->config)))
		{
			priv->autotry_number ++;

			if(priv->autotry_number >= hev_config_get_general_retrynumber(HEV_CONFIG(priv->config)))
			{
				priv->autotry_number = 0;
			}
			else
			{
				hev_server_start(self);
			}
		}
		break;
	}
	default:
		break;
	}
}

static gboolean hev_server_timeout_handler(gpointer data)
{
	HevServer * self = HEV_SERVER(data);
	HevServerPrivate * priv = NULL;

	g_return_val_if_fail(HEV_IS_SERVER(self), FALSE);

	priv = HEV_SERVER_GET_PRIVATE(self);

	if(priv->try_number >= HEV_SERVER_LOST_PACKET_TRY_NUMBER)
	{
		goto timeout;
	}

	switch(priv->state)
	{
	case HEV_SERVER_FINDING_SERVER:
	{
		hev_server_find_server(self);
		break;
	}
	case HEV_SERVER_AUTH_ACCOUNT_NAME:
	{
		hev_server_auth_account_name(self);
		break;
	}
	case HEV_SERVER_AUTH_ACCOUNT_PASSWD:
	{
		hev_server_auth_account_passwd(self);
		break;
	}
	case HEV_SERVER_AUTH_SUCC:
	case HEV_SERVER_NULL:
	default:
		return FALSE;
	}

	priv->try_number ++;

	return TRUE;

timeout:
	priv->state = HEV_SERVER_TIMEOUT;
	priv->try_number = 0;
	g_signal_emit(G_OBJECT(self), hev_server_signals[STATUS_CHANGED],
			0, priv->state);
	hev_server_close_io(self);

	return FALSE;
}

static gboolean hev_server_find_server(HevServer * self)
{
	HevServerPrivate * priv = NULL;

	g_return_val_if_fail(HEV_IS_SERVER(self), FALSE);

	priv = HEV_SERVER_GET_PRIVATE(self);
	return hev_sender_send_find_server(HEV_SENDER(priv->sender), priv->local_mac);
}

static gboolean hev_server_auth_account_name(HevServer * self)
{
	HevServerPrivate * priv = NULL;
	gchar * name = NULL;
	gboolean b = FALSE;

	g_return_val_if_fail(HEV_IS_SERVER(self), FALSE);

	priv = HEV_SERVER_GET_PRIVATE(self);
	name = hev_config_get_account_name(HEV_CONFIG(priv->config));
	if(NULL == name)
	{
		g_critical("[%s -> hev_config_get_account_name]", __FUNCTION__);
		return FALSE;
	}
	b = hev_sender_send_account_name(HEV_SENDER(priv->sender), priv->local_mac,
			priv->remote_mac, priv->packet[0x13], name);
	g_free(name);

	return b;
}

static gboolean hev_server_auth_account_passwd(HevServer * self)
{
	HevServerPrivate * priv = NULL;
	gchar * passwd = NULL;
	gboolean b = FALSE;

	g_return_val_if_fail(HEV_IS_SERVER(self), FALSE);

	priv = HEV_SERVER_GET_PRIVATE(self);
	passwd = hev_config_get_account_passwd(HEV_CONFIG(priv->config));
	if(NULL == passwd)
	{
		g_critical("[%s -> hev_config_get_account_passwd]", __FUNCTION__);
		return FALSE;
	}
	b = hev_sender_send_account_passwd(HEV_SENDER(priv->sender), priv->local_mac,
			priv->remote_mac, priv->packet[0x13], priv->packet[0x16], (guint8*)(priv->packet+0x17), passwd);
	g_free(passwd);

	return b;
}

static gboolean hev_server_logout(HevServer * self)
{
	HevServerPrivate * priv = NULL;

	g_return_val_if_fail(HEV_IS_SERVER(self), FALSE);

	priv = HEV_SERVER_GET_PRIVATE(self);
	return hev_sender_send_logout(HEV_SENDER(priv->sender), priv->local_mac, priv->remote_mac);
}

static gint hev_server_open_io(HevServer * self)
{
	HevServerPrivate * priv = NULL;
	gchar * interface = NULL;
	GError * error = NULL;

	g_return_val_if_fail(HEV_IS_SERVER(self), -1);

	priv = HEV_SERVER_GET_PRIVATE(self);

	interface = hev_config_get_general_interface(HEV_CONFIG(priv->config));
	if(NULL == interface)
	{
		return -1;
	}

	/* sender START */
	error = NULL;
	priv->sender = hev_sender_new(NULL, &error, interface);
	if(!HEV_IS_SENDER(priv->sender))
	{
		g_critical("[%s -> hev_sender_new]: %s", __FUNCTION__, error->message);
		g_error_free(error);
		goto fail;
	}
	/* sender END */

	/* receiver START */
	error = NULL;
	priv->receiver = hev_receiver_new(NULL, &error, interface);
	if(HEV_IS_RECEIVER(priv->receiver))
	{
		g_signal_connect(G_OBJECT(priv->receiver), "received",
				G_CALLBACK(hev_server_receiver_real_received), self);

		if(-1 == hev_receiver_set_filter(HEV_RECEIVER(priv->receiver), priv->local_mac))
		{
			g_object_unref(priv->receiver);
			priv->receiver = NULL;
			goto fail;
		}
	}
	else
	{
		g_critical("[%s -> hev_receiver_new]: %s", __FUNCTION__, error->message);
		g_error_free(error);
		goto fail;
	}
	/* receiver END */

	return 0;
fail:
	g_free(interface);
	return -1;
}

static void hev_server_close_io(HevServer * self)
{
	HevServerPrivate * priv = NULL;

	g_return_if_fail(HEV_IS_SERVER(self));

	priv = HEV_SERVER_GET_PRIVATE(self);

	/* receiver START */
	if(G_IS_OBJECT(priv->receiver))
	{
		g_object_unref(priv->receiver);

		priv->receiver = NULL;
	}
	/* receiver END */

	/* sender START */
	if(G_IS_OBJECT(priv->sender))
	{
		g_object_unref(priv->sender);

		priv->sender = NULL;
	}
	/* sender END */
}
